Відкрийте світ надшвидких і стійких веб-додатків. Цей вичерпний посібник розглядає передові стратегії кешування та політики керування Service Worker для глобальної аудиторії.
Опановуємо продуктивність фронтенду: глибоке занурення в політики керування кешем Service Worker
У сучасній веб-екосистемі продуктивність — це не додаткова функція, а фундаментальна вимога. Користувачі по всьому світу, з різними мережами від високошвидкісного оптоволокна до нестабільного 3G, очікують швидкої, надійної та захоплюючої взаємодії. Service Workers стали наріжним каменем у створенні таких веб-додатків нового покоління, зокрема прогресивних веб-додатків (PWA). Вони діють як програмований проксі-сервер між вашим додатком, браузером та мережею, надаючи розробникам безпрецедентний контроль над мережевими запитами та кешуванням.
Однак проста реалізація базової стратегії кешування — це лише перший крок. Справжня майстерність полягає в ефективному керуванні кешем. Некерований кеш може швидко стати проблемою, подаючи застарілий контент, займаючи надмірний дисковий простір і, зрештою, погіршуючи користувацький досвід, який мав би покращувати. Саме тут критично важливою стає чітко визначена політика керування кешем.
Цей вичерпний посібник виведе вас за межі основ кешування. Ми дослідимо мистецтво та науку керування життєвим циклом вашого кешу, від стратегічної інвалідації до інтелектуальних політик витіснення. Ми розповімо, як створювати надійні, самообслуговувані кеші, що забезпечують оптимальну продуктивність для кожного користувача, незалежно від його місцезнаходження чи якості мережі.
Основні стратегії кешування: огляд фундаментальних понять
Перш ніж занурюватися в політики керування, важливо мати тверде розуміння фундаментальних стратегій кешування. Ці стратегії визначають, як service worker реагує на подію fetch, і є будівельними блоками будь-якої системи керування кешем. Вважайте їх тактичними рішеннями, які ви приймаєте для кожного окремого запиту.
Cache First (або Cache Only)
Ця стратегія ставить швидкість понад усе, перевіряючи спочатку кеш. Якщо знайдено відповідну відповідь, вона негайно повертається без звернення до мережі. Якщо ні, запит надсилається до мережі, а відповідь (зазвичай) кешується для майбутнього використання. Варіант 'Cache Only' ніколи не звертається до мережі, що робить його ідеальним для ресурсів, які, як ви знаєте, вже є в кеші.
- Як це працює: Перевірити кеш -> Якщо знайдено, повернути. Якщо не знайдено, отримати з мережі -> Закешувати відповідь -> Повернути відповідь.
- Найкраще підходить для: "Оболонки" додатку — основних файлів HTML, CSS та JavaScript, які є статичними та рідко змінюються. Також ідеально для шрифтів, логотипів та версіонованих ресурсів.
- Глобальний вплив: Забезпечує миттєве завантаження, подібне до нативного додатку, що є вирішальним для утримання користувачів на повільних або ненадійних мережах.
Приклад реалізації:
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
// Return the cached response if it's found
if (cachedResponse) {
return cachedResponse;
}
// If not in cache, go to the network
return fetch(event.request);
})
);
});
Network First
Ця стратегія ставить у пріоритет свіжість даних. Вона завжди намагається спочатку отримати ресурс із мережі. Якщо мережевий запит успішний, вона повертає свіжу відповідь і зазвичай оновлює кеш. Лише якщо мережа недоступна (наприклад, користувач офлайн), вона повертає вміст з кешу.
- Як це працює: Отримати з мережі -> Якщо успішно, оновити кеш і повернути відповідь. Якщо невдало, перевірити кеш -> Повернути кешовану відповідь, якщо вона доступна.
- Найкраще підходить для: Ресурсів, що часто змінюються і для яких користувач завжди повинен бачити останню версію. Наприклад, API-запити для інформації про обліковий запис користувача, вміст кошика або термінові новини.
- Глобальний вплив: Забезпечує цілісність даних для критичної інформації, але може здаватися повільною на поганих з'єднаннях. Резервний варіант для офлайн-режиму є її ключовою особливістю стійкості.
Приклад реалізації:
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.then(networkResponse => {
// Also, update the cache with the new response
return caches.open('dynamic-cache').then(cache => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
})
.catch(() => {
// If the network fails, try to serve from the cache
return caches.match(event.request);
})
);
});
Stale-While-Revalidate
Ця стратегія, яку часто вважають найкращою з обох світів, забезпечує баланс між швидкістю та свіжістю. Спочатку вона негайно відповідає кешованою версією, забезпечуючи швидкий користувацький досвід. Одночасно вона надсилає запит до мережі для отримання оновленої версії. Якщо новіша версія знайдена, вона оновлює кеш у фоновому режимі. Користувач побачить оновлений вміст під час наступного візиту або взаємодії.
- Як це працює: Негайно відповісти кешованою версією. Потім отримати з мережі -> Оновити кеш у фоновому режимі для наступного запиту.
- Найкраще підходить для: Некритичного контенту, який виграє від актуальності, але для якого показ трохи застарілих даних є прийнятним. Наприклад, стрічки соціальних мереж, аватари або вміст статей.
- Глобальний вплив: Це фантастична стратегія для глобальної аудиторії. Вона забезпечує миттєву сприйману продуктивність, гарантуючи, що контент не стане занадто застарілим, і чудово працює за будь-яких умов мережі.
Приклад реалізації:
self.addEventListener('fetch', event => {
event.respondWith(
caches.open('dynamic-content-cache').then(cache => {
return cache.match(event.request).then(cachedResponse => {
const fetchPromise = fetch(event.request).then(networkResponse => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
// Return the cached response if available, while the fetch happens in the background
return cachedResponse || fetchPromise;
});
})
);
});
Суть справи: проактивні політики керування кешем
Вибір правильної стратегії отримання даних — це лише половина справи. Проактивна політика керування визначає, як ваші кешовані ресурси підтримуються з часом. Без неї сховище вашого PWA може заповнитися застарілими та нерелевантними даними. Цей розділ охоплює стратегічні, довгострокові рішення щодо здоров'я вашого кешу.
Інвалідація кешу: коли і як очищати дані
Інвалідація кешу відома як одна з найскладніших проблем у комп'ютерних науках. Мета полягає в тому, щоб користувачі отримували оновлений контент, коли він доступний, не змушуючи їх вручну очищати свої дані. Ось найефективніші методи інвалідації.
1. Версіонування кешів
Це найнадійніший і найпоширеніший метод керування оболонкою додатку. Ідея полягає в тому, щоб створювати новий кеш з унікальним, версіонованим ім'ям кожного разу, коли ви розгортаєте нову збірку вашого додатку з оновленими статичними ресурсами.
Процес працює наступним чином:
- Встановлення: Під час події `install` нового service worker створюється новий кеш (наприклад, `static-assets-v2`) і попередньо кешуються всі нові файли оболонки додатку.
- Активація: Як тільки новий service worker переходить у фазу `activate`, він отримує контроль. Це ідеальний час для очищення. Скрипт активації перебирає всі існуючі імена кешів і видаляє ті, що не відповідають поточній, активній версії кешу.
Практична порада: Це забезпечує чіткий розрив між версіями додатку. Користувачі завжди отримуватимуть найновіші ресурси після оновлення, а старі, невикористовувані файли автоматично видаляються, запобігаючи роздуванню сховища.
Приклад коду для очищення в події `activate`:
const STATIC_CACHE_NAME = 'static-assets-v2';
self.addEventListener('activate', event => {
console.log('Service Worker activating.');
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
// If the cache name is not our current static cache, delete it
if (cacheName !== STATIC_CACHE_NAME) {
console.log('Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});
2. Час життя (TTL) або максимальний вік (Max Age)
Деякі дані мають передбачуваний термін життя. Наприклад, відповідь API на запит про погоду може вважатися свіжою лише протягом години. Політика TTL передбачає зберігання мітки часу разом із кешованою відповіддю. Перед тим, як подати кешований елемент, ви перевіряєте його вік. Якщо він старший за визначений максимальний вік, ви розглядаєте це як промах кешу та отримуєте свіжу версію з мережі.
Хоча Cache API не підтримує це нативно, ви можете реалізувати це, зберігаючи метадані в IndexedDB або вбудовуючи мітку часу безпосередньо в заголовки об'єкта Response перед його кешуванням.
3. Інвалідація, ініційована користувачем
Іноді контроль має бути у користувача. Надання кнопки "Оновити дані" або "Очистити офлайн-дані" в налаштуваннях вашого додатку може бути потужною функцією. Це особливо цінно для користувачів з лімітованими або дорогими тарифними планами, оскільки це дає їм прямий контроль над сховищем та споживанням даних.
Щоб реалізувати це, ваша веб-сторінка може надіслати повідомлення активному service worker за допомогою API `postMessage()`. Service worker прослуховує це повідомлення і, отримавши його, може програмно очистити певні кеші.
Ліміти сховища кешу та політики витіснення
Сховище браузера — це обмежений ресурс. Кожен браузер виділяє певну квоту для сховища вашого домену (що включає Cache Storage, IndexedDB тощо). Коли ви наближаєтеся до цього ліміту або перевищуєте його, браузер може почати автоматично витісняти дані, часто починаючи з найменш використовуваного домену. Щоб запобігти такій непередбачуваній поведінці, розумно реалізувати власну політику витіснення.
Розуміння квот сховища
Ви можете програмно перевірити квоти сховища за допомогою Storage Manager API:
if ('storage' in navigator && 'estimate' in navigator.storage) {
navigator.storage.estimate().then(({usage, quota}) => {
console.log(`Using ${usage} out of ${quota} bytes.`);
const percentUsed = (usage / quota * 100).toFixed(2);
console.log(`You've used ${percentUsed}% of available storage.`);
});
}
Хоча це корисно для діагностики, логіка вашого додатку не повинна покладатися на це. Натомість вона повинна діяти захисно, встановлюючи власні розумні обмеження.
Реалізація політики максимальної кількості записів
Проста, але ефективна політика — обмежити кеш максимальною кількістю записів. Наприклад, ви можете вирішити зберігати лише 50 останніх переглянутих статей або 100 останніх зображень. Коли додається новий елемент, ви перевіряєте розмір кешу. Якщо він перевищує ліміт, ви видаляєте найстаріший елемент(и).
Концептуальна реалізація:
function addToCacheAndEnforceLimit(cacheName, request, response, maxEntries) {
caches.open(cacheName).then(cache => {
cache.put(request, response);
cache.keys().then(keys => {
if (keys.length > maxEntries) {
// Delete the oldest entry (first in the list)
cache.delete(keys[0]);
}
});
});
}
Реалізація політики найменш нещодавно використаного (LRU)
Політика LRU є більш витонченою версією політики максимальної кількості записів. Вона гарантує, що витісняються ті елементи, з якими користувач не взаємодіяв найдовше. Це, як правило, ефективніше, оскільки зберігає контент, який все ще актуальний для користувача, навіть якщо він був закешований давно.
Реалізація справжньої політики LRU є складною лише за допомогою Cache API, оскільки він не надає міток часу доступу. Стандартним рішенням є використання допоміжного сховища в IndexedDB для відстеження міток часу використання. Однак це ідеальний приклад того, де бібліотека може абстрагувати складність.
Практична реалізація з бібліотеками: знайомтеся, Workbox
Хоча важливо розуміти базові механізми, ручна реалізація цих складних політик керування може бути втомливою та схильною до помилок. Саме тут сяють бібліотеки, такі як Workbox від Google. Workbox надає готовий до використання в продакшені набір інструментів, що спрощують розробку service worker та інкапсулюють найкращі практики, включаючи надійне керування кешем.
Навіщо використовувати бібліотеку?
- Зменшує шаблонний код: Абстрагує низькорівневі виклики API у чистий, декларативний код.
- Вбудовані найкращі практики: Модулі Workbox розроблені на основі перевірених патернів для продуктивності та стійкості.
- Надійність: Обробляє крайні випадки та міжбраузерні невідповідності за вас.
Легке керування кешем з плагіном `workbox-expiration`
Плагін `workbox-expiration` — це ключ до простого та потужного керування кешем. Його можна додати до будь-якої вбудованої стратегії Workbox для автоматичного застосування політик витіснення.
Розгляньмо практичний приклад. Тут ми хочемо кешувати зображення з нашого домену, використовуючи стратегію `CacheFirst`. Ми також хочемо застосувати політику керування: зберігати максимум 60 зображень і автоматично видаляти будь-яке зображення, старше 30 днів. Крім того, ми хочемо, щоб Workbox автоматично очищав цей кеш, якщо ми зіткнемося з проблемами квоти сховища.
Приклад коду з Workbox:
import { registerRoute } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
// Cache images with a max of 60 entries, for 30 days
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'image-cache',
plugins: [
new ExpirationPlugin({
// Only cache a maximum of 60 images
maxEntries: 60,
// Cache for a maximum of 30 days
maxAgeSeconds: 30 * 24 * 60 * 60,
// Automatically clean up this cache if quota is exceeded
purgeOnQuotaError: true,
}),
],
})
);
Лише кількома рядками конфігурації ми реалізували складну політику, що поєднує `maxEntries` та `maxAgeSeconds` (TTL), з додатковим захистом від помилок квоти. Це значно простіше та надійніше, ніж ручна реалізація.
Розширені аспекти для глобальної аудиторії
Щоб створювати веб-додатки справді світового класу, ми повинні мислити ширше за власні високошвидкісні з'єднання та потужні пристрої. Чудова політика кешування — це та, що адаптується до контексту користувача.
Кешування з урахуванням пропускної здатності
The Network Information API дозволяє service worker отримувати інформацію про з'єднання користувача. Ви можете використовувати це для динамічної зміни вашої стратегії кешування.
- `navigator.connection.effectiveType`: Повертає 'slow-2g', '2g', '3g' або '4g'.
- `navigator.connection.saveData`: Логічне значення, що вказує, чи ввімкнув користувач у своєму браузері режим економії даних.
Приклад сценарію: Для користувача на з'єднанні '4g' ви можете використовувати стратегію `NetworkFirst` для API-запиту, щоб забезпечити отримання свіжих даних. Але якщо `effectiveType` — 'slow-2g' або `saveData` — true, ви можете перейти на стратегію `CacheFirst`, щоб надати пріоритет продуктивності та мінімізувати використання даних. Цей рівень емпатії до технічних та фінансових обмежень ваших користувачів може значно покращити їхній досвід.
Розділення кешів
Критично важливою найкращою практикою є ніколи не звалювати всі ваші кешовані ресурси в один гігантський кеш. Розділяючи ресурси на різні кеші, ви можете застосовувати до кожного з них окремі та відповідні політики керування.
- `app-shell-cache`: Містить основні статичні ресурси. Керується версіонуванням при активації.
- `image-cache`: Містить переглянуті користувачем зображення. Керується політикою LRU/максимальної кількості записів.
- `api-data-cache`: Містить відповіді API. Керується політикою TTL/`StaleWhileRevalidate`.
- `font-cache`: Містить веб-шрифти. Стратегія `Cache-first`, можна вважати постійним до наступної версії оболонки додатку.
Таке розділення забезпечує гранулярний контроль, роблячи вашу загальну стратегію ефективнішою та легшою для налагодження.
Висновок: створення стійких та продуктивних веб-додатків
Ефективне керування кешем Service Worker — це трансформаційна практика для сучасної веб-розробки. Вона піднімає додаток з рівня простого веб-сайту до стійкого, високопродуктивного PWA, що поважає пристрій та мережеві умови користувача.
Підсумуємо ключові моменти:
- Виходьте за рамки базового кешування: Кеш — це жива частина вашого додатку, яка потребує політики керування життєвим циклом.
- Поєднуйте стратегії та політики: Використовуйте фундаментальні стратегії (Cache First, Network First тощо) для окремих запитів та накладайте на них довгострокові політики керування (версіонування, TTL, LRU).
- Інвалідуйте розумно: Використовуйте версіонування кешу для оболонки вашого додатку та політики на основі часу або розміру для динамічного контенту.
- Використовуйте автоматизацію: Застосовуйте бібліотеки, такі як Workbox, для реалізації складних політик з мінімальним кодом, зменшуючи кількість помилок та покращуючи супровід.
- Мислите глобально: Проектуйте свої політики з урахуванням глобальної аудиторії. Розділяйте кеші та розглядайте адаптивні стратегії на основі умов мережі, щоб створити справді інклюзивний досвід.
Продумано реалізуючи ці політики керування кешем, ви можете створювати веб-додатки, які є не тільки блискавично швидкими, але й надзвичайно стійкими, забезпечуючи надійний та приємний досвід для кожного користувача, де б він не знаходився.